﻿using System;
using System.Diagnostics.Contracts;
using IWMAG.Core.EnhancedProperties;
using IWMAG.Core.EnhancedProperties.PropertyNodes;
using IWMAG.Core.EnhancedProperties.Extensions;

namespace IWMAG.Core.EnhancedProperties.ComputedValueProperties
{
    public static class ComputedValueExtensions
    {
        const int DEFAULT_COMPUTEDVALUE_PRIORITY = 10;
        public static void SetComputedValue<T>(this IPropertyContainer owner, PropertyBacker<T> backer, Func<T, T> computeDelegate, int priority = DEFAULT_COMPUTEDVALUE_PRIORITY)
        {
            VerifyPropertyComputable(owner, backer, computeDelegate);

            var actual = backer.GetOrCreatePropertyNodeList(owner);

            InsertComputedValueIntoPropertyListAndNotifyPropertyManager(owner, backer, computeDelegate, priority, actual);
        }

        public static bool SetComputedValue<T>(this TryPropertyBacker tryBacker,PropertyBacker<T> backer, Func<T, T> computeDelegate, int priority = DEFAULT_COMPUTEDVALUE_PRIORITY)
        {
            var c = CheckPropertyComputable(tryBacker.Container, backer, computeDelegate);
            if (!c)
            {
                return false;
            }

            var actual = backer.GetOrCreatePropertyNodeList(tryBacker.Container);

            InsertComputedValueIntoPropertyListAndNotifyPropertyManager(tryBacker.Container, backer, computeDelegate, priority, actual);

            return true;
        }


        public static int AddComputedValue<T>(this IPropertyContainer owner, PropertyBacker<T> backer, Func<T, T> computeDelegate)
        {
            VerifyPropertyComputable<T>(owner, backer, computeDelegate);

            var actual = backer.GetOrCreatePropertyNodeList(owner);
            var highest = actual.GetHighestPriority() + 1;

            InsertComputedValueIntoPropertyListAndNotifyPropertyManager<T>(owner, backer, computeDelegate, highest, actual);

            return highest;
        }

        public static bool AddComputedValue<T>(this TryPropertyBacker tryBacker,PropertyBacker<T> backer ,Func<T, T> computeDelegate, ref int priority)
        {
            var c = CheckPropertyComputable(tryBacker.Container, backer, computeDelegate);

            if (!c)
            {
                return false;
            }

            var actual = backer.GetOrCreatePropertyNodeList(tryBacker.Container);
            var highest = actual.GetHighestPriority() + 1;

            InsertComputedValueIntoPropertyListAndNotifyPropertyManager<T>(tryBacker.Container, backer, computeDelegate, highest, actual);

            priority =  highest;
            return true;
        }

        private static void InsertComputedValueIntoPropertyListAndNotifyPropertyManager<T>(IPropertyContainer owner, PropertyBacker<T> backer, Func<T, T> computeDelegate, int priority, PropertyNodeList<T> actual)
        {
            actual.AddOrModifyListMember(priority, (i) => new ComputedNode<T>(i, computeDelegate), (node) =>
            {
                node.SetNodeComputation(computeDelegate);
                return true;
            });

            PropertyManager.RegisterPropertyAffected(owner.UniqueID, backer.UniqueID);
        }





        private static bool CheckPropertyComputable<T>(IPropertyContainer owner, PropertyBacker<T> backer, Func<T, T> computeDelegate)
        {
            Contract.Requires(owner != null);
            Contract.Requires(backer != null);
            Contract.Requires(computeDelegate != null);

            return backer.Metadata.CanSetComputedValue;
        }

        private static void VerifyPropertyComputable<T>(IPropertyContainer owner, PropertyBacker<T> backer, Func<T, T> computeDelegate)
        {
            if (!CheckPropertyComputable(owner,backer,computeDelegate))
            {
                throw new NotSupportedException("Property cannot have a computed value.");
            }
        }
    }
}
